package miao

import (
	"bufio"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	log "github.com/sirupsen/logrus"
	"miao-va-host/global"
	"miao-va-host/model"
	"net"
	"sync"
	"time"
)

type Client struct {
	ClientId   int64
	tcp        net.Conn
	inChan     chan model.ClientInstruct
	outChan    chan model.ClientResponse
	closeChan  chan byte
	mutex      sync.Mutex
	IsClosed   bool
	pipOutChan chan string
	pipErrChan chan error
	pipCancel  context.CancelFunc
}

func (c *Client) HandMsg(inst model.ClientInstruct) {
	inst.ClientType = global.ClientType
	var err error
	switch inst.Code {
	case global.SetDeviceId:
		err = c.setDeviceId(inst)
	case global.StartGuarder:
		c.startGuarder(inst)
	case global.StopGuarder:
		c.stopGuarder(inst)
	case global.ExecutePlugin:
		err = c.execPlugin(inst)

	default:
		log.Errorf("undefined message:%v ", inst)
	}
	if err != nil {
		log.Errorf("处理客户端指定时发生错误，指令：%v ,err:%v", inst, err)
	}
}
func InitConnection() (va *Client) {
	va = &Client{
		tcp:        nil,
		inChan:     make(chan model.ClientInstruct, 1000),
		outChan:    make(chan model.ClientResponse, 1000),
		closeChan:  make(chan byte, 1),
		IsClosed:   true,
		pipOutChan: make(chan string),
		pipErrChan: make(chan error),
	}
	va.dial()
	return
}

type PingPcLoad struct {
	CPU    float64
	Memory float64
}

// Ping 发送ping心跳信息
func (c *Client) Ping() {
	if c.IsClosed {
		c.dial()
	} else {
		f1, f2 := getLoad()
		l := PingPcLoad{
			CPU:    f1,
			Memory: f2,
		}
		var (
			bs  []byte
			err error
		)
		if bs, err = json.Marshal(l); err != nil {
			return
		}
		err = c.WriteMessage(model.ClientResponse{
			DeviceId:   c.ClientId,
			ClientType: global.ClientType,
			ClientTime: time.Now().Unix(),
			Code:       global.Heartbeat,
			Timely:     1,
			Content: model.PluginResult{
				Result: string(bs),
			},
		})
		if err != nil {
			log.Error(err)
		}
	}
}

// dial 拨号，连接服务器
func (c *Client) dial() {
	defer c.mutex.Unlock()
	c.mutex.Lock()
	if c.IsClosed {
		con, err := net.Dial("tcp", fmt.Sprintf("%s:%d", global.Conf.Tcp.Ip, global.Conf.Tcp.Port))
		if err != nil {
			log.Errorf("连接服务器失败:%v", err)
			c.IsClosed = true
		} else {
			log.Debug("连接服务器成功")
			c.tcp = con
			c.closeChan = make(chan byte, 1)
			c.IsClosed = false
			// 启动读信息的协程
			go c.readLoop()
			// 启动写协程
			go c.writeLoop()
			// 举手
			err = c.handsUp()
			if err != nil {
				log.Error(err)
				c.Close()
			}
		}
	}
}

func (c *Client) ReaderMessage() (msg model.ClientInstruct, err error) {
	select {
	case msg = <-c.inChan:
	case <-c.closeChan:
		err = errors.New("connection is closed")
	}
	return
}

func (c *Client) WriteMessage(msg model.ClientResponse) (err error) {
	select {
	case c.outChan <- msg:
	case <-c.closeChan:
		err = errors.New("connection is closed")
	}
	return
}

func (c *Client) Close() {
	// 关闭操作只能执行一次
	c.mutex.Lock()
	if c.tcp != nil {
		_ = c.tcp.Close()
	}
	if !c.IsClosed {
		close(c.closeChan)
		c.IsClosed = true
	}
	c.mutex.Unlock()
}

func (c *Client) readLoop() {
	var (
		bs  []byte
		err error
	)
	for {
		if !c.IsClosed {
			if bs, err = Decode(bufio.NewReader(c.tcp)); err != nil {
				log.Error(err)
				goto ERR
			}
			if string(bs) == "ping" {
				continue
			}
			log.Debug("收到消息：", string(bs))
			inst := model.ClientInstruct{}
			err = json.Unmarshal(bs, &inst)
			if err != nil {
				log.Error(err)
				goto ERR
			}
			select {
			case c.inChan <- inst:
			case <-c.closeChan:
				log.Error(err)
				goto ERR
			}
		}
	}
	// 发生错误时应该退出循环，否则协程无法正常回收
ERR:
	c.Close()
}

func (c *Client) writeLoop() {
	for {
		select {
		case msg := <-c.outChan:
			if !c.IsClosed {
				bs, err := Encode(msg)
				if err != nil {
					log.Error("tcp消息编码错误")
				}
				_, err = c.tcp.Write(bs)
				if err != nil {
					goto ERR
				}
			}
		case <-c.closeChan:
			goto ERR
		}

	}
ERR:
	c.Close()
}
